/*
 * Copyright 2010 Destil, Libor Tvrdik
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cz.destil.gpsaveraging;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SimpleAdapter;
import android.widget.TextView;

/** @author Destil */
public class GPSAveraging extends Activity {

	private static final String ALT = "alt"; //$NON-NLS-1$
	private static final String GPS = "gps"; //$NON-NLS-1$
	private static final String LATLON = "latlon"; //$NON-NLS-1$
	private static final String LATLON_ACCURACY = "latlon_accuracy"; //$NON-NLS-1$

	private LocationManager locManager;
	private volatile boolean averaging = false;
	private boolean returnToDifferentApp = false;

	private GpsAverageList measurements;
	private Exporter exporter;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		// Inflate our UI from its XML layout description.
		setContentView(R.layout.main);
		((Button) findViewById(R.id.button)).setOnClickListener(clickListener);
		((Button) findViewById(R.id.send)).setOnClickListener(clickListener2);
		((Button) findViewById(R.id.map)).setOnClickListener(clickListener3);
		((Button) findViewById(R.id.export)).setOnClickListener(clickListener4);
		locManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		measurements = new GpsAverageList();
		exporter = new Exporter(getApplicationContext());

		// react to intents from other apps
		if (getIntent().getAction().equals("menion.android.locus.GET_POINT")
				|| getIntent().getAction().equals(
						"cz.destil.gpsaveraging.AVERAGED_LOCATION")) {
			returnToDifferentApp = true;
		}
	}

	@Override
	public void onStart() {
		super.onStart();
		((Button) findViewById(R.id.button)).setEnabled(false);
		locManager.requestLocationUpdates(GPS, 0, 0, locListener);
		if (!locManager.isProviderEnabled(GPS)) {
			((TextView) findViewById(R.id.latlon))
					.setText(R.string.YouDontHaveGPSEnabled);
			hideFinalButtons();
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.main_menu, menu);
		return true;
	}

	/**
	 * Called when menu selected
	 */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		switch (item.getItemId()) {
		case R.id.about:
			Intent intent = new Intent(Intent.ACTION_VIEW,
					Uri.parse(getString(R.string.app_link)));
			startActivity(intent);
			return true;
		case R.id.compatible_apps:
			viewCompatibleAppsDialog();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}

	private void viewCompatibleAppsDialog() {
		final AlertDialog.Builder alert = new AlertDialog.Builder(this);
		alert.setTitle(R.string.CompatibleApps);
		alert.setCancelable(true);
		List<HashMap<String, String>> items = new LinkedList<HashMap<String, String>>();
		final HashMap<String, String> locus = new HashMap<String, String>();
		locus.put("title", getString(R.string.Locus));
		locus.put("desc", getString(R.string.LocusDescription));
		locus.put("url", getString(R.string.LocusUrl));
		items.add(locus);
		SimpleAdapter adapter = new SimpleAdapter(this, items, R.layout.compatible_list_item, new String[] {"title", "desc"}, new int[] {R.id.title, R.id.desc});
		alert.setSingleChoiceItems(adapter, 0, new DialogInterface.OnClickListener() {
			
			public void onClick(DialogInterface dialog, int which) {
				Intent intent2 = new Intent(Intent.ACTION_VIEW,
						Uri.parse(locus.get("url")));
				startActivity(intent2);
			}
		});
		alert.setNegativeButton(R.string.Close, new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
			}
		});
		alert.show();
	}

	private void hideFinalButtons() {
		((Button) findViewById(R.id.send)).setVisibility(View.INVISIBLE);
		((Button) findViewById(R.id.map)).setVisibility(View.INVISIBLE);
		((Button) findViewById(R.id.export)).setVisibility(View.INVISIBLE);
	}

	@Override
	public void onStop() {
		super.onStop();
		locManager.removeUpdates(locListener);
		stop();
	}

	void start() {
		averaging = true;
		showAveragingUI();
		((TextView) findViewById(R.id.button)).setText(R.string.StopAveraging);
		hideFinalButtons();
		(new WorkerThread()).start();
	}

	private void showAveragingUI() {
		((TextView) findViewById(R.id.avglatlon_label))
				.setVisibility(View.VISIBLE);
		((TextView) findViewById(R.id.avglatlon)).setVisibility(View.VISIBLE);
		((TextView) findViewById(R.id.avglatlonaccuracy))
				.setVisibility(View.VISIBLE);
		((TextView) findViewById(R.id.avgalt)).setVisibility(View.VISIBLE);
		((TextView) findViewById(R.id.hint)).setVisibility(View.VISIBLE);
		((TextView) findViewById(R.id.measurements))
				.setVisibility(View.VISIBLE);
	}

	void stop() {
		averaging = false;
		((Button) findViewById(R.id.button)).setText(R.string.NewAveraging);
		if (returnToDifferentApp) {
			Intent intent = new Intent();
			intent.putExtra("name", getString(R.string.AveragedLocation));
			intent.putExtra("latitude", measurements.getLatitude());
			intent.putExtra("longitude", measurements.getLongitude());
			intent.putExtra("altitude", measurements.getAltitude());
			intent.putExtra("accuracy", (double)measurements.getAccuracy());
			setResult(RESULT_OK, intent);
			finish();
		}
		{
			((TextView) findViewById(R.id.hint)).setVisibility(View.INVISIBLE);
			showFinalButtons();
		}
	}

	private void showFinalButtons() {
		((TextView) findViewById(R.id.send)).setVisibility(View.VISIBLE);
		((TextView) findViewById(R.id.map)).setVisibility(View.VISIBLE);
		((TextView) findViewById(R.id.export)).setVisibility(View.VISIBLE);
	}

	/** Create email with the average coordinates and display email service. */
	void send() {

		try {

			Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
			shareIntent.setType("text/plain"); //$NON-NLS-1$
			shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
					getString(R.string.EmailSubject));
			shareIntent.putExtra(android.content.Intent.EXTRA_TEXT,
					exporter.toString(measurements));

			startActivity(Intent.createChooser(shareIntent,
					getString(R.string.SelectApplication)));

		} catch (Exception e) {
			e.printStackTrace();
			infoMessage(R.string.ApplicationCantGenerateEmail);
		}
	}

	/** Show the average coordinates on map. */
	void map() {

		final StringBuilder uri = new StringBuilder("geo:"); //$NON-NLS-1$
		uri.append(measurements.getLatitude()).append(',')
				.append(measurements.getLongitude());

		final Intent intent = new Intent(android.content.Intent.ACTION_VIEW,
				Uri.parse(uri.toString()));
		startActivity(intent);
	}

	/** Store the average coordinates to file. */
	void export() {

		try {

			final Calendar now = Calendar.getInstance();
			final File storeDirectory = new File(
					Environment.getExternalStorageDirectory(),
					getString(R.string.app_directory));

			if (!storeDirectory.exists()) {
				storeDirectory.mkdirs();
			}

			if (!storeDirectory.exists()) {
				infoMessage(getString(R.string.CantCreateStorageDirectory,
						storeDirectory));
				return;
			}

			final String dateFormatPattern = "%1$td-%<tm-%<tY_%<tH-%<tM-%<tS.%2$s"; //$NON-NLS-1$

			final File gpxFile = new File(storeDirectory, String.format(
					dateFormatPattern, now, "gpx")); //$NON-NLS-1$
			gpxFile.createNewFile();
			final BufferedWriter gpxOut = new BufferedWriter(new FileWriter(
					gpxFile));
			exporter.toGPXString(measurements, gpxOut);
			gpxOut.close();

			final File kmlFile = new File(storeDirectory, String.format(
					dateFormatPattern, now, "kml")); //$NON-NLS-1$
			kmlFile.createNewFile();
			final BufferedWriter kmlOut = new BufferedWriter(new FileWriter(
					kmlFile));
			exporter.toKMLString(measurements, kmlOut);
			kmlOut.close();

			infoMessage(getString(R.string.AverageLocationExportedToFiles,
					storeDirectory, gpxFile.getName(), kmlFile.getName()));

		} catch (Exception e) {
			e.printStackTrace();
			infoMessage(getString(R.string.ApplicationCantWriteFile,
					e.getMessage()));
		}
	}

	LocationListener locListener = new LocationListener() {

		public void onLocationChanged(Location loc) {
			((TextView) findViewById(R.id.latlon)).setText(exporter
					.formatGPS(loc));
			((TextView) findViewById(R.id.latlonaccuracy)).setText(exporter
					.formatAccuracy(loc));
			((TextView) findViewById(R.id.alt)).setText(exporter
					.formatAltitude(loc.getAltitude()));
			((Button) findViewById(R.id.button)).setEnabled(true);
		}

		public void onStatusChanged(String arg0, int status, Bundle arg2) {
		}

		public void onProviderEnabled(String arg0) {
		}

		public void onProviderDisabled(String arg0) {
			((TextView) findViewById(R.id.latlon))
					.setText(getString(R.string.YouDontHaveGPSEnabled));
			((TextView) findViewById(R.id.latlonaccuracy)).setText(""); //$NON-NLS-1$
			((TextView) findViewById(R.id.alt)).setText(""); //$NON-NLS-1$
			((Button) findViewById(R.id.button)).setEnabled(false);
			stop();
			hideFinalButtons();
		}
	};
	OnClickListener clickListener = new OnClickListener() {

		public void onClick(View arg0) {
			if (averaging) {
				stop();
			} else {
				start();
			}
		}
	};
	OnClickListener clickListener2 = new OnClickListener() {

		public void onClick(View arg0) {
			send();
		}
	};
	OnClickListener clickListener3 = new OnClickListener() {

		public void onClick(View arg0) {
			map();
		}
	};
	OnClickListener clickListener4 = new OnClickListener() {

		public void onClick(View arg0) {
			export();
		}
	};
	// Handler receives coords from worker thread
	final Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {

			final String latlon = msg.getData().getString(LATLON);
			final String latlonAccuracy = msg.getData().getString(
					LATLON_ACCURACY);
			final String alt = msg.getData().getString(ALT);

			((TextView) findViewById(R.id.avglatlon)).setText(latlon);
			((TextView) findViewById(R.id.avglatlonaccuracy))
					.setText(latlonAccuracy);
			((TextView) findViewById(R.id.avgalt)).setText(alt);
			((TextView) findViewById(R.id.measurements)).setText(getString(
					R.string.Measurements, measurements.size()));
		}
	};

	class WorkerThread extends Thread {

		@Override
		public void run() {
			measurements.clean();
			while (averaging) {
				// the averaging algorithm - weightening by horizontal accuracy
				// increasing counters
				measurements.add(locManager.getLastKnownLocation(GPS));

				// sending results
				Message msg = handler.obtainMessage();
				Bundle b = new Bundle();
				b.putString(LATLON,
						exporter.formatGPS(measurements.getLocation()));
				b.putString(LATLON_ACCURACY,
						exporter.formatAccuracy(measurements.getLocation()));
				b.putString(ALT,
						exporter.formatAltitude(measurements.getAltitude()));
				msg.setData(b);
				handler.sendMessage(msg);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException ex) {
					Logger.getLogger(GPSAveraging.class.getName()).log(
							Level.SEVERE, null, ex);
				}
			}
		}
	}

	/**
	 * Create info dialog with one OK button and show it.
	 * 
	 * @param resId
	 *            in {@link R#string}
	 */
	private void infoMessage(int resId) {
		infoMessage(getString(resId));
	}

	/**
	 * Create info dialog with one OK button and show it.
	 * 
	 * @param text
	 *            for display on message
	 */
	private void infoMessage(String text) {

		final Builder alertDialog = new AlertDialog.Builder(this);
		alertDialog.setMessage(text);
		alertDialog.setPositiveButton(R.string.OK,
				new DismissDialogClickListener());
		alertDialog.show();
	}

	/** Calculates distance between two gps lat/lon pairs */
	public static double distance(double lat1, double lng1, double lat2,
			double lng2) {
		double earthRadius = 3958.75;
		double dLat = Math.toRadians(lat2 - lat1);
		double dLng = Math.toRadians(lng2 - lng1);
		double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
				+ Math.cos(Math.toRadians(lat1))
				* Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2)
				* Math.sin(dLng / 2);
		double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
		double dist = earthRadius * c;

		int meterConversion = 1609;
		return (dist * meterConversion);
	}

	private static class DismissDialogClickListener implements
			android.content.DialogInterface.OnClickListener {

		public void onClick(DialogInterface di, int arg1) {
			di.dismiss();
		}
	}

}
